#include <cstdlib>
#include <fstream>
#include <iostream>
#include <cstdio>
#include <cmath>
#include "MPC.h"
#include "AI.h"
#include "evaluator.h"

using namespace std;

/* parameter used for predicting future evaluation */
double gradient[NUM_STAGES][2];
double bias[NUM_STAGES][2];
double deviation[NUM_STAGES][2];

/* Boundary on an evaluation value being valid */
const int BOUND = 150 * TERMINAL_DIFF_WEIGHT;

void PerformSearch(gameState gs[], BitBoard blocked, BoardState player[])
{
    int depth1 = PAIR1_DEEP - PAIR1_SHALLOW;
    int depth2 = PAIR2_DEEP - PAIR2_SHALLOW;
    
    fstream filestr;
    filestr.open(MPC_DATA, fstream::out | fstream::app);
    
    if (!filestr.is_open())
        return;
        
    for (int stage = 0; stage < NUM_STAGES - 1; stage++)
    {
        int input = evaluateGame(gs[stage], blocked, player[stage], stage);
        filestr << input << endl;
        
        int target1 = Minimax(gs[stage], player[stage+1], depth1, -100000000, 100000000, stage, blocked, -1);
        filestr << target1 << endl;
        
        int target2 = Minimax(gs[stage], player[stage+1], depth2, -100000000, 100000000, stage, blocked, -1);
        filestr << target2 << endl;
    }
    
    filestr.close();
}

double SumOfSquaredError(double a, double c, int x[], int t[], int game_count)
{
    double sum = 0.0f;
    
    for (int n = 0; n < game_count; n++)
    {
        if (absolute(x[n]) > BOUND || absolute(t[n]) > BOUND)
            continue;

        double diff = t[n] - (a * x[n] + c);
        sum += diff * diff;
    }
    
    return sum;
}

double MeanError(double a, double c, int x[], int t[], int game_count)
{
    double sum = 0.0f;
    int count = 0;
    for (int n = 0; n < game_count; n++)
    {
        if (absolute(x[n]) > BOUND || absolute(t[n]) > BOUND)
        {
            count++;
            continue;
        }

        double diff = t[n] - (a * x[n] + c);
        sum += diff;
    }
    
    //if (count != 0)
    //    cerr << count << " out of bounds " << endl;
        
    return sum / game_count;
}

void FitMPC()
{
    // maximum steps to take when fitting the linear regression
    const int MAX_STEPS = 1000;
    const int MAX_GAMES = 1000;
    const double alpha = 1e-9;
    
    fstream filestr;
    filestr.open(MPC_DATA, fstream::in);
    
    if (!filestr.is_open())
        return;
        
    char buffer[255];
    
    // input and targets
    int input[NUM_STAGES][MAX_GAMES];
    int target[2][NUM_STAGES][MAX_GAMES];
    
    // linear regression params
    double gradient[NUM_STAGES][2], bias[NUM_STAGES][2];
    double deviation[NUM_STAGES][2];
    
    int game_count = 0;
    
    while (game_count < MAX_GAMES && !filestr.eof())
    {
        for (int stage = 0; stage < NUM_STAGES - 1; stage++)
        {
            filestr.getline(buffer, 255);
            input[stage][game_count] = atoi(buffer);
            
            filestr.getline(buffer, 255);
            target[0][stage][game_count] = atoi(buffer);
            
            filestr.getline(buffer, 255);
            target[1][stage][game_count] = atoi(buffer);
        }   
        game_count++;
    }
    
    game_count--;
    
    /*
    for (int i = 0; i < game_count; i++)
    {
        for (int stage = 0; stage < NUM_STAGES - 1; stage++)
        {
            cerr << "input: " << input[stage][i];
            cerr << " target: " << target[0][stage][i] << " " << target[1][stage][i] << endl;
        }
        cin.get();
    }
    */
    
    // fit the input and targets to linear regressions t = ax + c
    // compute mean and standard deviation of the errors
    for (int i = 0; i < 2; i++)
    {
        for (int stage = 0; stage < NUM_STAGES - 1; stage++)
        { 
            // initialise a and c values
            
            double & a = gradient[stage][i];
            double & c = bias[stage][i];
            a = c = 0;
            
            assert(gradient[stage][i] == 0 && bias[stage][i] == 0);
            
            int * x = &(input[stage][0]);
            int * t = &(target[i][stage][0]);
            
            for (int j = 0; j < MAX_STEPS; j++)
            {
                double old_a = a;
                double old_c = c;
                
                for (int n = 0; n < game_count; n++)
                {
                    // ignore input and targets which lay outside the normal boundary
                    if (absolute(x[n]) > BOUND || absolute(t[n]) > BOUND)
                        continue;
                        
                    // fit each t[n] = a x[n] + c using gradient of the squared error
                    double diff = t[n] - (a * x[n] + c); 
                    if (x[n] != 0) 
                        a = a + alpha * diff / x[n] / 2;
                    c = c + alpha * diff / 2;
                    
                    //double diff2 = t[n] - (a * x[n] + c);
                    
                    //cerr << diff << " " << diff2 << endl;
                    //cin.get();
                }
                
                
                // speculartive jumping
                double delta_a = a - old_a;
                double delta_c = c - old_c;
                double exp = 1.0f;
                
                double sumErrorNew = SumOfSquaredError(a, c, x, t, game_count);
                double sumErrorOld = sumErrorNew + 1e10;
                while (sumErrorNew < sumErrorOld)
                {
                    sumErrorOld = sumErrorNew;
                    a += delta_a * exp;
                    exp *= 2;
                    sumErrorNew = SumOfSquaredError(a, c, x, t, game_count);
                }
                exp /= 2;
                a -= delta_a * exp;
                
                
                exp = 1.0f;
                sumErrorNew = SumOfSquaredError(a, c, x, t, game_count);
                sumErrorOld = sumErrorNew + 1e10;
                while (sumErrorNew < sumErrorOld)
                {
                    sumErrorOld = sumErrorNew;
                    c += delta_c * exp;
                    exp *= 2;
                    sumErrorNew = SumOfSquaredError(a, c, x, t, game_count);
                }
                exp /= 2;
                c -= delta_c * exp;
            }
            
            double avgError = SumOfSquaredError(a, c, x, t, game_count) / game_count;
            deviation[stage][i] = sqrt(avgError);
            
            if (avgError > 1e8)
            {
                cerr << "stage " << stage << " average squared error: " << avgError << endl;
                cin.get();
            }
        }
    }
    
    for (int i = 0; i < 2; i++)
    {
        for (int stage = 0; stage < NUM_STAGES - 1; stage++)
        {
            double meanError = MeanError(gradient[stage][i], bias[stage][i], input[stage], target[i][stage], game_count);
            printf("%2d: t[%d] = %7.4f * x[%d] + %7.4f   e_mean = %5.2f  deviation = %5.2f\n",
                    stage, i, gradient[stage][i], i, bias[stage][i], meanError, deviation[stage][i]);
                    
        }
    }
    
    WriteMPC(gradient, bias, deviation);
}

void WriteMPC(double a[][2], double c[][2], double deviation[][2])
{    
    fstream filestr;
    filestr.open(MPC_FILE, fstream::out);
    
    if (!filestr.is_open())
        return;
    for (int stage = 0; stage < NUM_STAGES - 1; stage++)
    {
        filestr << a[stage][0] << endl;
        filestr << c[stage][0] << endl;
        filestr << deviation[stage][0] << endl;
        
        filestr << a[stage][1] << endl;
        filestr << c[stage][1] << endl;
        filestr << deviation[stage][1] << endl;
    }
    filestr.close();
}

void InitialiseMPC()
{
    fstream filestr;
    filestr.open(MPC_FILE, fstream::in);
    
    if (!filestr.is_open())
        return;
        
    char buffer[255];
    for (int stage = 0; stage < NUM_STAGES - 1; stage++)
    {
        filestr.getline(buffer, 255);
        gradient[stage][0] = atof(buffer);
        
        filestr.getline(buffer, 255);
        bias[stage][0] = atof(buffer);
        
        filestr.getline(buffer, 255);
        deviation[stage][0] = atof(buffer);
        
        filestr.getline(buffer, 255);
        gradient[stage][1] = atof(buffer);
        
        filestr.getline(buffer, 255);
        bias[stage][1] = atof(buffer);
        
        filestr.getline(buffer, 255);
        deviation[stage][1] = atof(buffer);
    }
    filestr.close();
    
    /*
    for (int i = 0; i < 2; i++)
    {
        for (int stage = 0; stage < NUM_STAGES - 1; stage++)
        {
            printf("%2d: t[%d] = %7.4f * x[%d] + %7.4f   deviation = %5.2f\n",
                    stage, i, gradient[stage][i], i, bias[stage][i], deviation[stage][i]);
                    
        }
    }
    cin.get();
    */
}

int GetBoundLow(int alpha, int stage, int depth)
{
    int bound;
    if (depth == PAIR1_SHALLOW)
        bound = (alpha - deviation[stage+depth][0]*3 - bias[stage+depth][0]) / gradient[stage+depth][0];
    else
        bound = (alpha - deviation[stage+depth][1]*3 - bias[stage+depth][1]) / gradient[stage+depth][1];
        
    if (bound < -BOUND)
        bound = 0;
    if (bound > BOUND)
        bound = 0;
        
    return bound;
}

int GetBoundHigh(int beta, int stage, int depth)
{
    int bound;
    if (depth == PAIR1_SHALLOW)
        bound = (beta + deviation[stage+depth][0]*2 - bias[stage+depth][0]) / gradient[stage+depth][0];
    else
        bound = (beta + deviation[stage+depth][1]*2 - bias[stage+depth][1]) / gradient[stage+depth][1];
        
    if (bound > BOUND)
        bound = 0;
    if (bound < -BOUND)
        bound = 0; 
    return bound;
}


/* Perform end cut on the selected node */
bool performEndCut(int alpha, int beta, int x, int depth, int stage)
{
    double a, c, dev;
    if (depth = CUT_DEPTH1)
    {
        a = gradient[stage][0];
        c = bias[stage][0];
        dev = deviation[stage][0];
    }
    else
    {
        a = gradient[stage][1];
        c = bias[stage][1];
        dev = deviation[stage][1];
    }
    
    if (a * x + c + 2 * dev < alpha)
        return true;
    if (a * x + c - 2 * dev > beta)
        return true;
        
    return false;
}
